Skip to content

Conversation

@TORUS0818
Copy link
Owner

思考ログ:
- 再帰と同等の処理をloopに変換するのに時間がかかってしまった。。
- https://github.com/hroc135/leetcode/pull/43/files#r2002294824
- 思考のプロセスにどこか不自然な部分があるのだろうか。。
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、うーん、素直かといわれると、ループを回している途中の引き継ぎの状態を考えてみましょう。
n = a * 2^k + b で、bit_mask = 2^(k-1) なんですよね。
x^a = powered_x
で、残りのタスクは
x^n = powered_x ^(2^k) * x^b = (x^a)^(2^k) * x^b = x^(a * 2^k + b)
ですよね。

これ、見た瞬間に読み取れますか。

ちなみに、再帰とは対応していませんね。
再帰は下の桁からかけています。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

読み取れないですね。。
ただ、どう書くと素直に変換できるのか、うまく自分の中で整理できませんでした。

ちなみに下から掛けてる認識でしたが、違ってますでしょうか。

例えばx^6を求めるとして、再帰だと、
myPow(6) = myPow(3)^2 = (x * myPow(1)^2)^2 = (x * (x * myPow(0))^2)^2 = (x * (x * 1)^2)^2

こんなイメージで、ループでも、
(x * 1) -> (x * 1)^2 -> (x * (x * 1)^2)^2

と計算できている認識でした。
ただ、余分にxを描ける必要があるかどうか(例では1回目と2回目のループが必要)はnの上位ビットからみていく必要があるので、bit_mask = 1 << n.bit_length() - 1から確認していってます。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、再帰を self.myPow(x*x, n // 2) と勝手に思っていました。

ビットシフトするたびに2乗するというのは、速いでしょうからライブラリーの実装などどういう状況で呼ばれるか分からないものには適していると思います。一方で、枝の実装で、読む方を優先したいならば、少しアルゴリズムを変えてやりかたがあるかもしれません。

result = 1
current_bit = 1 << (n.bit_length() - 1)
current_n = 0
while current_n < n:
    result *= result
    current_n *= 2
    if n & current_bit:
        result *= x
        current_n += 1
    current_bit >>= 1
return result

が、まあ、しかし、「上のビットから回している」「全体を自乗すると指数が倍になる」がどちらにしても分かりにくいと思います。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私の感覚はこういう方が素直です。n x を破壊していないし、base bit の関係がはっきりしているからですね。

result = 1
bit = 1
base = x
while bit <= n:
    if n & bit:
        result *= base
    base *= base
    bit <<= 1 

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
意識してみます。

powered_x *= x

x *= x
n >>= 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こっちは、n を破壊しているために関係が見にくくなっているのだろうと思います。こちらのほうがまだましですが、この引き継ぎの瞬間、powered_x, x, n と答え(あと引数の元々の x と n)の関係はどうなっていますか。
答え = powered_x * x^n ですか。で、2^s <= (元々の n) / n となる最大の s とすると x = (元々の x) ^ (2^s) ですか。

元々の n, x を破壊しているのでややこしいことが起きています。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

はい、、ループに苦戦してずっと考えていたので、自分の中に色々前提が出来上がってしまっていて、逆にこれが自然に見えます。
ただ、しばらく経って読み返すと、よく分からなくなっている可能性が高い気がします。

Comment on lines +129 to +131
while bit_mask:
powered_x *= powered_x
if n & bit_mask:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit_maskを用いず、n だけ使って1ビットずつずらしながら、1の位にビットがあるかどうかを見ていくのでも良いかもと感じました。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、下でやってますね。

Comment on lines +191 to +193
return x * self.myPow(x ** 2, n // 2)
else:
return self.myPow(x ** 2, n // 2)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

好みですが、self.myPow全体を2乗するのでも良いかもです。

return self.myPow(1 / x, -n)

if n % 2 == 0:
return self.myPow(x, n // 2) * self.myPow(x, n // 2)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 回同じ関数を同じ引数で呼び出すより、 return self.myPow(x, n // 2) ** 2 としたほうが分かりやすいと感じました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants